######################################################################
#
# DESCRIPTION: CMake configuration file for Verilator
#
# Include it in your CMakeLists.txt using:
#
#     find_package(verilator)
#
#  This script adds a verilate function.
#
#     add_executable(simulator <your-c-sources>)
#     verilate(simulator SOURCES <your-hdl-sources>)
#
# Copyright 2003-2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
#
######################################################################

cmake_minimum_required(VERSION 3.19)

# Prefer VERILATOR_ROOT from environment
if(DEFINED ENV{VERILATOR_ROOT})
    set(VERILATOR_ROOT "$ENV{VERILATOR_ROOT}" CACHE PATH "VERILATOR_ROOT")
endif()

set(VERILATOR_ROOT "${CMAKE_CURRENT_LIST_DIR}" CACHE PATH "VERILATOR_ROOT")

find_program(
    VERILATOR_BIN
    NAMES verilator_bin verilator_bin.exe
    HINTS ${VERILATOR_ROOT}/bin
    ENV VERILATOR_ROOT
    NO_CMAKE_PATH
    NO_CMAKE_ENVIRONMENT_PATH
    NO_CMAKE_SYSTEM_PATH
)

if(NOT VERILATOR_ROOT)
    message(
        FATAL_ERROR
        "VERILATOR_ROOT cannot be detected. Set it to the appropriate directory (e.g. /usr/share/verilator) as an environment variable or CMake define."
    )
endif()

if(NOT VERILATOR_BIN)
    message(FATAL_ERROR "Cannot find verilator_bin excecutable.")
endif()

set(verilator_FOUND 1)

include(CheckCXXSourceCompiles)
function(_verilator_check_cxx_libraries LIBRARIES RESVAR)
    # Check whether a particular link option creates a valid executable
    set(_VERILATOR_CHECK_CXX_LINK_OPTIONS_SRC "int main() {return 0;}\n")
    set(CMAKE_REQUIRED_FLAGS)
    set(CMAKE_REQUIRED_DEFINITIONS)
    set(CMAKE_REQUIRED_INCLUDES)
    set(CMAKE_REQUIRED_LINK_OPTIONS)
    set(CMAKE_REQUIRED_LIBRARIES ${LIBRARIES})
    set(CMAKE_REQUIRED_QUIET)
    check_cxx_source_compiles(
        "${_VERILATOR_CHECK_CXX_LINK_OPTIONS_SRC}"
        "${RESVAR}"
    )
    set("${RESVAR}" "${${RESVAR}}" PARENT_SCOPE)
endfunction()

# Check compiler flag support. Skip on MSVC, these are all GCC flags.
if(NOT CMAKE_CXX_COMPILER_ID MATCHES MSVC)
    if(NOT DEFINED VERILATOR_CFLAGS OR NOT DEFINED VERILATOR_MT_CFLAGS)
        include(CheckCXXCompilerFlag)
        foreach(FLAG @CFG_CXX_FLAGS_CMAKE@)
            string(MAKE_C_IDENTIFIER ${FLAG} FLAGNAME)
            check_cxx_compiler_flag(${FLAG} ${FLAGNAME})
            if(${FLAGNAME})
                list(APPEND VERILATOR_CFLAGS ${FLAG})
            endif()
        endforeach()
        foreach(FLAG @CFG_LDFLAGS_THREADS_CMAKE@)
            string(MAKE_C_IDENTIFIER ${FLAG} FLAGNAME)
            _verilator_check_cxx_libraries("${FLAG}" ${FLAGNAME})
            if(${FLAGNAME})
                list(APPEND VERILATOR_MT_CFLAGS ${FLAG})
            endif()
        endforeach()
    endif()
endif()

if(${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang")
    add_link_options(-Wl,-U,__Z15vl_time_stamp64v,-U,__Z13sc_time_stampv)
endif()

define_property(
    TARGET
    PROPERTY VERILATOR_THREADED
    BRIEF_DOCS "Deprecated and has no effect (ignored)"
    FULL_DOCS "Deprecated and has no effect (ignored)"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TRACE_THREADED
    BRIEF_DOCS "Verilator multithread tracing enabled"
    FULL_DOCS "Verilator multithread tracing enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TIMING
    BRIEF_DOCS "Verilator timing enabled"
    FULL_DOCS "Verilator timing enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_COVERAGE
    BRIEF_DOCS "Verilator coverage enabled"
    FULL_DOCS "Verilator coverage enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TRACE
    BRIEF_DOCS "Verilator trace enabled"
    FULL_DOCS "Verilator trace enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TRACE_FST
    BRIEF_DOCS "Verilator FST trace enabled"
    FULL_DOCS "Verilator FST trace enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TRACE_SAIF
    BRIEF_DOCS "Verilator SAIF trace enabled"
    FULL_DOCS "Verilator SAIF trace enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TRACE_VCD
    BRIEF_DOCS "Verilator VCD trace enabled"
    FULL_DOCS "Verilator VCD trace enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_SYSTEMC
    BRIEF_DOCS "Verilator SystemC enabled"
    FULL_DOCS "Verilator SystemC enabled"
)

define_property(
    TARGET
    PROPERTY VERILATOR_TRACE_STRUCTS
    BRIEF_DOCS "Verilator trace structs enabled"
    FULL_DOCS "Verilator trace structs enabled"
)

function(json_get_string RET JSON SECTION VARIABLE)
    string(JSON JV ERROR_VARIABLE STATUS GET "${JSON}" ${SECTION} ${VARIABLE})
    if(NOT ${STATUS} STREQUAL "NOTFOUND")
        set(JV "")
    endif()
    set(${RET} ${JV} PARENT_SCOPE)
endfunction()

function(json_get_bool RET JSON SECTION VARIABLE)
    string(JSON JV GET "${JSON}" ${SECTION} ${VARIABLE})
    if(JV)
        set(${RET} 1 PARENT_SCOPE)
    else()
        set(${RET} 0 PARENT_SCOPE)
    endif()
endfunction()

function(json_get_int RET JSON SECTION VARIABLE)
    string(JSON JV GET "${JSON}" ${SECTION} ${VARIABLE})
    set(${RET} ${JV} PARENT_SCOPE)
endfunction()

function(json_get_submodules SUBMODULES NSUBMODULES JSON)
    string(JSON JV ERROR_VARIABLE STATUS GET "${JSON}" submodules)

    if(NOT ${STATUS} STREQUAL "NOTFOUND")
        set(${SUBMODULES} "" PARENT_SCOPE)
        set(${NSUBMODULES} 0 PARENT_SCOPE)
        return()
    endif()

    string(JSON L ERROR_VARIABLE STATUS LENGTH "${JSON}" submodules)

    math(EXPR L "${L}-1")

    set(${SUBMODULES} ${JV} PARENT_SCOPE)
    set(${NSUBMODULES} ${L} PARENT_SCOPE)
endfunction()

function(json_get_list RET JSON SECTION VARIABLE)
    string(JSON L ERROR_VARIABLE STATUS LENGTH "${JSON}" ${SECTION} ${VARIABLE})

    if(NOT ${STATUS} STREQUAL "NOTFOUND" OR NOT ${L})
        set(${RET} "" PARENT_SCOPE)
        return()
    endif()

    math(EXPR L "${L}-1")

    foreach(I RANGE ${L})
        string(JSON JV GET "${JSON}" ${SECTION} ${VARIABLE} ${I})
        if(NOT JL)
            string(APPEND JL "${JV}")
        else()
            string(APPEND JL " ${JV}")
        endif()
    endforeach()

    set(${RET} ${JL} PARENT_SCOPE)
endfunction()

function(verilate TARGET)
    cmake_parse_arguments(
        VERILATE
        "COVERAGE;SYSTEMC;TRACE_FST;TRACE_SAIF;TRACE_VCD;TRACE;TRACE_STRUCTS"
        "PREFIX;TOP_MODULE;THREADS;TRACE_THREADS;DIRECTORY"
        "SOURCES;VERILATOR_ARGS;INCLUDE_DIRS;OPT_SLOW;OPT_FAST;OPT_GLOBAL"
        ${ARGN}
    )

    if(VERILATE_TRACE)
        if(NOT VERILATE_TRACE_VCD)
            set(VERILATE_TRACE_VCD TRUE)
        endif()
        message(DEPRECATION "The `TRACE` argument is deprecated. Please use `TRACE_VCD` instead.")
    endif()

    if(NOT VERILATE_SOURCES)
        message(FATAL_ERROR "Need at least one source")
    endif()

    if(NOT VERILATE_PREFIX)
        list(LENGTH VERILATE_SOURCES NUM_SOURCES)
        if(${NUM_SOURCES} GREATER 1)
            message(WARNING "Specify PREFIX if there are multiple SOURCES")
        endif()
        list(GET VERILATE_SOURCES 0 TOPSRC)
        get_filename_component(_SRC_NAME ${TOPSRC} NAME_WE)
        string(MAKE_C_IDENTIFIER V${_SRC_NAME} VERILATE_PREFIX)
    endif()

    if(VERILATE_TOP_MODULE)
        list(APPEND VERILATOR_ARGS --top ${VERILATE_TOP_MODULE})
    endif()

    if(VERILATE_THREADS)
        list(APPEND VERILATOR_ARGS --threads ${VERILATE_THREADS})
    endif()

    if(VERILATE_TRACE_THREADS)
        list(APPEND VERILATOR_ARGS --trace-threads ${VERILATE_TRACE_THREADS})
    endif()

    if(VERILATE_COVERAGE)
        list(APPEND VERILATOR_ARGS --coverage)
    endif()

    if(VERILATE_SYSTEMC)
        list(APPEND VERILATOR_ARGS --sc)
    else()
        list(APPEND VERILATOR_ARGS --cc)
    endif()

    if(VERILATE_TRACE AND VERILATE_TRACE_FST)
        message(FATAL_ERROR "Cannot have both TRACE and TRACE_FST")
    endif()

    if(VERILATE_TRACE_FST AND VERILATE_TRACE_VCD)
        message(FATAL_ERROR "Cannot have both TRACE_FST and TRACE_VCD")
    endif()

    if(VERILATE_TRACE_SAIF AND VERILATE_TRACE_VCD)
        message(FATAL_ERROR "Cannot have both TRACE_SAIF and TRACE_VCD")
    endif()

    if(VERILATE_TRACE)
        list(APPEND VERILATOR_ARGS --trace-vcd)
    endif()

    if(VERILATE_TRACE_FST)
        list(APPEND VERILATOR_ARGS --trace-fst)
    endif()

    if(VERILATE_TRACE_SAIF)
        list(APPEND VERILATOR_ARGS --trace-saif)
    endif()

    if(VERILATE_TRACE_VCD)
        list(APPEND VERILATOR_ARGS --trace-vcd)
    endif()

    if(VERILATE_TRACE_STRUCTS)
        list(APPEND VERILATOR_ARGS --trace-structs)
    endif()

    foreach(INC ${VERILATE_INCLUDE_DIRS})
        list(APPEND VERILATOR_ARGS -y "${INC}")
    endforeach()

    string(TOLOWER ${CMAKE_CXX_COMPILER_ID} COMPILER)
    if(COMPILER STREQUAL "appleclang")
        set(COMPILER clang)
    elseif(NOT COMPILER MATCHES "^msvc$|^clang$")
        set(COMPILER gcc)
    endif()

    set(OBJCACHE "" CACHE STRING "Path for ccache, auto-detected if empty")
    option(OBJCACHE_ENABLED "Compile Verilator with ccache" ON)

    if(OBJCACHE_ENABLED)
        if(OBJCACHE STREQUAL "")
            find_program(OBJCACHE_PATH ccache)
            if(OBJCACHE_PATH STREQUAL "OBJCACHE_PATH-NOTFOUND")
                set(OBJCACHE_PATH "")
            endif()
        else()
            set(OBJCACHE_PATH "${OBJCACHE}")
        endif()
        if(NOT OBJCACHE_PATH STREQUAL "")
            execute_process(
                COMMAND "${OBJCACHE_PATH}" --version
                OUTPUT_VARIABLE objcache_version
            )
            string(
                REGEX MATCH
                "[^\n\r]+"
                objcache_version
                "${objcache_version}"
            )
            message(
                STATUS
                "Found ccache: ${OBJCACHE_PATH} (\"${objcache_version}\")"
            )
            set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${OBJCACHE_PATH}")
        endif()
    endif()

    get_target_property(BINARY_DIR "${TARGET}" BINARY_DIR)
    get_target_property(TARGET_NAME "${TARGET}" NAME)
    set(VDIR
        "${BINARY_DIR}/CMakeFiles/${TARGET_NAME}.dir/${VERILATE_PREFIX}.dir"
    )

    if(VERILATE_DIRECTORY)
        set(VDIR "${VERILATE_DIRECTORY}")
    endif()

    file(MAKE_DIRECTORY ${VDIR})

    set(VERILATOR_COMMAND
        "${CMAKE_COMMAND}"
        -E
        env
        "VERILATOR_ROOT=${VERILATOR_ROOT}"
        "${VERILATOR_BIN}"
        --compiler
        ${COMPILER}
        --prefix
        ${VERILATE_PREFIX}
        --Mdir
        ${VDIR}
        --make
        json
        ${VERILATOR_ARGS}
        ${VERILATE_VERILATOR_ARGS}
        ${VERILATE_SOURCES}
    )

    set(VARGS_FILE "${VDIR}/verilator_args.txt")
    set(VCMAKE "${VDIR}/${VERILATE_PREFIX}.cmake")
    set(VCMAKE_COPY "${VDIR}/${VERILATE_PREFIX}_copy.cmake")

    if(NOT EXISTS "${VARGS_FILE}" OR NOT EXISTS "${VCMAKE_COPY}")
        set(VERILATOR_OUTDATED ON)
    else()
        file(READ "${VARGS_FILE}" PREVIOUS_VERILATOR_COMMAND)
        if(NOT VERILATOR_COMMAND STREQUAL PREVIOUS_VERILATOR_COMMAND)
            set(VERILATOR_OUTDATED ON)
        endif()
    endif()

    if(VERILATOR_OUTDATED)
        message(STATUS "Executing Verilator...")
        execute_process(
            COMMAND ${VERILATOR_COMMAND}
            WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
            RESULT_VARIABLE _VERILATOR_RC
            OUTPUT_VARIABLE _VERILATOR_OUTPUT
            ERROR_VARIABLE _VERILATOR_OUTPUT
        )
        if(_VERILATOR_RC)
            string(
                REPLACE
                ";"
                " "
                VERILATOR_COMMAND_READABLE
                "${VERILATOR_COMMAND}"
            )
            message("Verilator command: \"${VERILATOR_COMMAND_READABLE}\"")
            message("Output:\n${_VERILATOR_OUTPUT}")
            message(
                FATAL_ERROR
                "Verilator command failed (return code=${_VERILATOR_RC})"
            )
        endif()

        file(READ ${VDIR}/${VERILATE_PREFIX}.json MANIFEST)

        json_get_string(JSYSTEM_PERL "${MANIFEST}" system perl)
        json_get_string(JSYSTEM_PYTHON3 "${MANIFEST}" system python3)
        json_get_string(JSYSTEM_VERILATOR_ROOT "${MANIFEST}" system verilator_root)
        json_get_string(JSYSTEM_VERILATOR_SOLVER "${MANIFEST}" system verilator_solver)

        json_get_list(JOPTIONS_CFLAGS "${MANIFEST}" options cflags)
        json_get_list(JOPTIONS_LDFLAGS "${MANIFEST}" options ldflags)
        json_get_bool(JOPTIONS_SYSTEM_C "${MANIFEST}" options system_c)
        json_get_bool(JOPTIONS_COVERAGE "${MANIFEST}" options coverage)
        json_get_bool(JOPTIONS_USE_TIMING "${MANIFEST}" options use_timing)
        json_get_int(JOPTIONS_THREADS "${MANIFEST}" options threads)
        json_get_bool(JOPTIONS_TRACE_FST "${MANIFEST}" options trace_fst)
        json_get_bool(JOPTIONS_TRACE_SAIF "${MANIFEST}" options trace_saif)
        json_get_bool(JOPTIONS_TRACE_VCD "${MANIFEST}" options trace_vcd)

        json_get_list(JSOURCES_GLOBAL "${MANIFEST}" sources global)
        json_get_list(JSOURCES_CLASSES_SLOW "${MANIFEST}" sources classes_slow)
        json_get_list(JSOURCES_CLASSES_FAST "${MANIFEST}" sources classes_fast)
        json_get_list(JSOURCES_SUPPORT_SLOW "${MANIFEST}" sources support_slow)
        json_get_list(JSOURCES_SUPPORT_FAST "${MANIFEST}" sources support_fast)
        json_get_list(JSOURCES_USER_CLASSES "${MANIFEST}" sources user_classes)
        json_get_list(JSOURCES_DEPS "${MANIFEST}" sources deps)

        file(WRITE ${VDIR}/${VERILATE_PREFIX}.cmake
            "# Verilated -*- CMake -*-\n"
            "# DESCRIPTION: Verilator output: CMake include script with class lists\n"
            "#\n"
            "# This CMake script lists generated Verilated files, for including in higher level CMake scripts.\n"
            "# This file is meant to be consumed by the verilate() function,\n"
            "# which becomes available after executing `find_package(verilator).\n\n"
            "### Constants...\n"
            "set(PERL \"${JSYSTEM_PERL}\" CACHE FILEPATH \"Perl executable (from \$PERL, defaults to 'perl' if not set)\")\n"
            "set(PYTHON3 \"${JSYSTEM_PYTHON3}\" CACHE FILEPATH \"Python3 executable (from \$PYTHON3, defaults to 'python3' if not set)\")\n"
            "set(VERILATOR_ROOT \"${JSYSTEM_VERILATOR_ROOT}\" CACHE PATH \"Path to Verilator kit (from $VERILATOR_ROOT)\")\n"
            "set(VERILATOR_SOLVER \"${JSYSTEM_VERILATOR_SOLVER}\" CACHE STRING \"Default SMT solver for constrained randomization (from \$VERILATOR_SOLVER)\")\n\n"
            "### Compiler flags...\n"
            "# User CFLAGS (from -CFLAGS on Verilator command line)\n"
            "set(${VERILATE_PREFIX}_USER_CFLAGS ${JOPTIONS_CFLAGS})\n"
            "# User LDLIBS (from -LDFLAGS on Verilator command line)\n"
            "set(${VERILATE_PREFIX}_USER_LDLIBS ${JOPTIONS_LDFLAGS})\n\n"
            "### Switches...\n"
            "# SystemC output mode?  0/1 (from --sc)\n"
            "set(${VERILATE_PREFIX}_SC ${JOPTIONS_SYSTEM_C})\n"
            "# Coverage output mode?  0/1 (from --coverage)\n"
            "set(${VERILATE_PREFIX}_COVERAGE ${JOPTIONS_COVERAGE})\n"
            "# Timing mode?  0/1\n"
            "set(${VERILATE_PREFIX}_TIMING ${JOPTIONS_USE_TIMING})\n"
            "# Threaded output mode?  1/N threads (from --threads)\n"
            "set(${VERILATE_PREFIX}_THREADS ${JOPTIONS_THREADS})\n"
            "# FST Tracing output mode? 0/1 (from --trace-fst)\n"
            "set(${VERILATE_PREFIX}_TRACE_FST ${JOPTIONS_TRACE_FST})\n\n"
            "# SAIF Tracing output mode? 0/1 (from --trace-saif)\n"
            "set(${VERILATE_PREFIX}_TRACE_SAIF ${JOPTIONS_TRACE_SAIF})\n\n"
            "# VCD Tracing output mode?  0/1 (from --trace-vcd)\n"
            "set(${VERILATE_PREFIX}_TRACE_VCD ${JOPTIONS_TRACE_VCD})\n"
            "### Sources...\n"
            "# Global classes, need linked once per executable\n"
            "set(${VERILATE_PREFIX}_GLOBAL ${JSOURCES_GLOBAL})\n"
            "# Generated module classes, non-fast-path, compile with low/medium optimization\n"
            "set(${VERILATE_PREFIX}_CLASSES_SLOW ${JSOURCES_CLASSES_SLOW})\n"
            "# Generated module classes, fast-path, compile with highest optimization\n"
            "set(${VERILATE_PREFIX}_CLASSES_FAST ${JSOURCES_CLASSES_FAST})\n"
            "# Generated support classes, non-fast-path, compile with low/medium optimization\n"
            "set(${VERILATE_PREFIX}_SUPPORT_SLOW ${JSOURCES_SUPPORT_SLOW})\n"
            "# Generated support classes, fast-path, compile with highest optimization\n"
            "set(${VERILATE_PREFIX}_SUPPORT_FAST ${JSOURCES_SUPPORT_FAST})\n"
            "# All dependencies\n"
            "set(${VERILATE_PREFIX}_DEPS ${JSOURCES_DEPS})\n"
            "# User .cpp files (from .cpp's on Verilator command line)\n"
            "set(${VERILATE_PREFIX}_USER_CLASSES ${JSOURCES_USER_CLASSES})\n"
        )

        json_get_submodules(JSUBMODULES JNSUBMODULES "${MANIFEST}")

        if(JNSUBMODULES)
            file(APPEND ${VDIR}/${VERILATE_PREFIX}.cmake
                "# Verilate hierarchical blocks\n"
                "get_target_property(TOP_TARGET_NAME \"\${TARGET}\" NAME)\n"
            )

            foreach(I RANGE ${JNSUBMODULES})
                json_get_string(JSUBMODULE_PREFIX "${JSUBMODULES}" ${I} prefix)
                json_get_string(JSUBMODULE_TOP "${JSUBMODULES}" ${I} top)
                json_get_list(JSUBMODULE_DEPS "${JSUBMODULES}" ${I} deps)
                json_get_string(JSUBMODULE_DIRECTORY "${JSUBMODULES}" ${I} directory)
                json_get_list(JSUBMODULE_SOURCES "${JSUBMODULES}" ${I} sources)
                json_get_list(JSUBMODULE_CFLAGS "${JSUBMODULES}" ${I} cflags)
                json_get_string(JSUBMODILE_VERILATOR_ARGS "${JSUBMODULES}" ${I} verilator_args)

                set(SUBMODULE_CMAKE "")
                set(SUBMODULE_VERILATE_ARGS "")

                if(NOT ${I} STREQUAL ${JNSUBMODULES})
                    string(APPEND SUBMODULE_CMAKE
                        "add_library(${JSUBMODULE_PREFIX} STATIC)\n"
                        "target_link_libraries(\${TOP_TARGET_NAME} PRIVATE ${JSUBMODULE_PREFIX})\n"
                    )

                    if(JSUBMODULE_DEPS)
                        string(APPEND SUBMODULE_CMAKE "target_link_libraries(${JSUBMODULE_PREFIX} INTERFACE ${JSUBMODULE_DEPS})\n")
                    endif()

                    string(APPEND SUBMODULE_VERILATE_ARGS "${JSUBMODULE_PREFIX} PREFIX ${JSUBMODULE_PREFIX} TOP_MODULE ${JSUBMODULE_TOP} DIRECTORY ${JSUBMODULE_DIRECTORY} SOURCES ${JSUBMODULE_SOURCES}")
                else()
                    string(APPEND SUBMODULE_CMAKE "# Verilate the top module that refers to lib-create wrappers of above\n")
                    string(APPEND SUBMODULE_VERILATE_ARGS "\${TOP_TARGET_NAME} PREFIX ${JSUBMODULE_PREFIX} TOP_MODULE ${JSUBMODULE_TOP} DIRECTORY ${JSUBMODULE_DIRECTORY} SOURCES ${JSUBMODULE_SOURCES}")
                endif()

                if(JSUBMODILE_VERILATOR_ARGS)
                    string(APPEND SUBMODULE_VERILATE_ARGS " VERILATOR_ARGS -f ${JSUBMODILE_VERILATOR_ARGS}")
                endif()

                if(JSUBMODULE_CFLAGS)
                    string(APPEND SUBMODULE_VERILATE_ARGS " -CFLAGS ${JSUBMODULE_CFLAGS}")
                endif()

                file(APPEND ${VDIR}/${VERILATE_PREFIX}.cmake
                    "${SUBMODULE_CMAKE}"
                    "verilate(${SUBMODULE_VERILATE_ARGS})\n"
                )
            endforeach()
        endif()

        execute_process(
            COMMAND "${CMAKE_COMMAND}" -E copy "${VCMAKE}" "${VCMAKE_COPY}"
        )
    endif()
    file(WRITE "${VARGS_FILE}" "${VERILATOR_COMMAND}")

    include("${VCMAKE_COPY}")

    set(GENERATED_C_SOURCES
        ${${VERILATE_PREFIX}_CLASSES_FAST}
        ${${VERILATE_PREFIX}_CLASSES_SLOW}
        ${${VERILATE_PREFIX}_SUPPORT_FAST}
        ${${VERILATE_PREFIX}_SUPPORT_SLOW}
    )
    # No need for .h's as the .cpp will get written same time
    set(GENERATED_SOURCES ${GENERATED_C_SOURCES})

    add_custom_command(
        OUTPUT ${GENERATED_SOURCES} "${VCMAKE}"
        COMMAND ${VERILATOR_COMMAND}
        WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
        DEPENDS "${VERILATOR_BIN}" ${${VERILATE_PREFIX}_DEPS}
        VERBATIM
    )
    # Reconfigure if file list has changed
    # (check contents rather than modified time to avoid unnecessary reconfiguration)
    add_custom_command(
        OUTPUT "${VCMAKE_COPY}"
        COMMAND
            "${CMAKE_COMMAND}" -E copy_if_different "${VCMAKE}" "${VCMAKE_COPY}"
        DEPENDS "${VCMAKE}"
        VERBATIM
    )

    if(${VERILATE_PREFIX}_COVERAGE)
        # If any verilate() call specifies COVERAGE, define VM_COVERAGE in the final build
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_COVERAGE ON)
    endif()

    if(${VERILATE_PREFIX}_SC)
        # If any verilate() call specifies SYSTEMC, define VM_SC in the final build
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_SYSTEMC ON)
    endif()

    if(${VERILATE_PREFIX}_TRACE_FST)
        # If any verilate() call specifies TRACE_FST, define VM_TRACE_FST in the final build
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE ON)
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE_FST ON)
    endif()

    if(${VERILATE_PREFIX}_TRACE_SAIF)
        # If any verilate() call specifies TRACE_SAIF, define VM_TRACE_SAIF in the final build
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE ON)
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE_SAIF ON)
    endif()

    if(${VERILATE_PREFIX}_TRACE_VCD)
        # If any verilate() call specifies TRACE, define VM_TRACE_VCD in the final build
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE ON)
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE_VCD ON)
    endif()

    if(${VERILATE_PREFIX}_TRACE_STRUCTS)
        set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE_STRUCTS ON)
    endif()

    # Add the compile flags only on Verilated sources
    target_include_directories(${TARGET} PUBLIC ${VDIR})
    target_sources(
        ${TARGET}
        PRIVATE
            ${GENERATED_SOURCES}
            "${VCMAKE_COPY}"
            ${${VERILATE_PREFIX}_GLOBAL}
            ${${VERILATE_PREFIX}_USER_CLASSES}
    )
    foreach(_VSOURCE ${VERILATE_SOURCES} ${${VERILATE_PREFIX}_DEPS})
        get_filename_component(_VSOURCE "${_VSOURCE}" ABSOLUTE BASE_DIR)
        list(APPEND VHD_SOURCES "${_VSOURCE}")
    endforeach()
    target_sources(${TARGET} PRIVATE ${VHD_SOURCES})

    # Add the compile flags only on Verilated sources
    foreach(
        VSLOW
        ${${VERILATE_PREFIX}_CLASSES_SLOW}
        ${${VERILATE_PREFIX}_SUPPORT_SLOW}
    )
        foreach(OPT_SLOW ${VERILATE_OPT_SLOW} ${${VERILATE_PREFIX}_USER_CFLAGS})
            set_property(
                SOURCE "${VSLOW}"
                APPEND_STRING
                PROPERTY COMPILE_FLAGS " ${OPT_SLOW}"
            )
        endforeach()
    endforeach()
    foreach(
        VFAST
        ${${VERILATE_PREFIX}_CLASSES_FAST}
        ${${VERILATE_PREFIX}_SUPPORT_FAST}
    )
        foreach(OPT_FAST ${VERILATE_OPT_FAST} ${${VERILATE_PREFIX}_USER_CFLAGS})
            set_property(
                SOURCE "${VFAST}"
                APPEND_STRING
                PROPERTY COMPILE_FLAGS " ${OPT_FAST}"
            )
        endforeach()
    endforeach()
    foreach(VGLOBAL ${${VERILATE_PREFIX}_GLOBAL})
        foreach(
            OPT_GLOBAL
            ${VERILATE_OPT_GLOBAL}
            ${${VERILATE_PREFIX}_USER_CFLAGS}
        )
            set_property(
                SOURCE "${VGLOBAL}"
                APPEND_STRING
                PROPERTY COMPILE_FLAGS " ${OPT_GLOBAL}"
            )
        endforeach()
    endforeach()

    target_include_directories(
        ${TARGET}
        PUBLIC "${VERILATOR_ROOT}/include" "${VERILATOR_ROOT}/include/vltstd"
    )
    target_compile_definitions(
        ${TARGET}
        PRIVATE
            VM_COVERAGE=$<BOOL:$<TARGET_PROPERTY:VERILATOR_COVERAGE>>
            VM_SC=$<BOOL:$<TARGET_PROPERTY:VERILATOR_SYSTEMC>>
            VM_TRACE=$<BOOL:$<TARGET_PROPERTY:VERILATOR_TRACE>>
            VM_TRACE_VCD=$<BOOL:$<TARGET_PROPERTY:VERILATOR_TRACE_VCD>>
            VM_TRACE_FST=$<BOOL:$<TARGET_PROPERTY:VERILATOR_TRACE_FST>>
            VM_TRACE_SAIF=$<BOOL:$<TARGET_PROPERTY:VERILATOR_TRACE_SAIF>>
    )

    target_link_libraries(${TARGET} PUBLIC ${${VERILATE_PREFIX}_USER_LDLIBS})

    target_link_libraries(${TARGET} PUBLIC ${VERILATOR_MT_CFLAGS})

    target_compile_features(${TARGET} PRIVATE cxx_std_11)

    if(${VERILATE_PREFIX}_TIMING)
        check_cxx_compiler_flag(-fcoroutines-ts COROUTINES_TS_FLAG)
        target_compile_options(
            ${TARGET}
            PRIVATE
                $<IF:$<BOOL:${COROUTINES_TS_FLAG}>,-fcoroutines-ts,-fcoroutines>
        )
    endif()
endfunction()

function(_verilator_find_systemc)
    if(NOT TARGET Verilator::systemc)
        # Find SystemC include file "systemc.h" in the following order:
        # 1. SYSTEMC_INCLUDE (environment) variable
        # 2. SYSTEMC_ROOT (environment) variable
        # 3. SYSTEMC (environment) variable
        # 4. Use CMake module provided by SystemC installation
        #    (eventually requires CMAKE_PREFIX_PATH set)

        find_path(
            SYSTEMC_INCLUDEDIR
            NAMES systemc.h
            HINTS "${SYSTEMC_INCLUDE}   "
            ENV SYSTEMC_INCLUDE
        )
        find_path(
            SYSTEMC_INCLUDEDIR
            NAMES systemc.h
            HINTS "${SYSTEMC_ROOT}"
            ENV SYSTEMC_ROOT
            PATH_SUFFIXES include
        )
        find_path(
            SYSTEMC_INCLUDEDIR
            NAMES systemc.h
            HINTS "${SYSTEMC}"
            ENV SYSTEMC
            PATH_SUFFIXES include
        )

        # Find SystemC library in the following order:
        # 1. SYSTEMC_LIBDIR (environment) variable
        # 2. SYSTEMC_ROOT (environment) variable
        # 3. SYSTEMC (environment) variable
        # 4. Use CMake module provided by SystemC installation
        #    (eventually requires CMAKE_PREFIX_PATH set)

        # Find SystemC using include and library paths
        find_library(
            SYSTEMC_LIBRARY
            NAMES systemc
            HINTS "${SYSTEMC_LIBDIR}"
            ENV SYSTEMC_LIBDIR
        )
        find_library(
            SYSTEMC_LIBRARY
            NAMES systemc
            HINTS "${SYSTEMC_ROOT}"
            ENV SYSTEMC_ROOT
            PATH_SUFFIXES lib
        )
        find_library(
            SYSTEMC_LIBRARY
            NAMES systemc
            HINTS "${SYSTEMC}"
            ENV SYSTEMC
            PATH_SUFFIXES lib
        )

        if(SYSTEMC_INCLUDEDIR AND SYSTEMC_LIBRARY)
            add_library(Verilator::systemc INTERFACE IMPORTED)
            set_target_properties(
                Verilator::systemc
                PROPERTIES
                    INTERFACE_INCLUDE_DIRECTORIES "${SYSTEMC_INCLUDEDIR}"
                    INTERFACE_LINK_LIBRARIES "${SYSTEMC_LIBRARY}"
            )
            return()
        endif()

        find_package(SystemCLanguage QUIET)
        if(SystemCLanguage_FOUND)
            add_library(Verilator::systemc INTERFACE IMPORTED)
            set_target_properties(
                Verilator::systemc
                PROPERTIES INTERFACE_LINK_LIBRARIES "SystemC::systemc"
            )
            return()
        endif()

        message(
            "SystemC not found. This can be fixed by doing either of the following steps:"
        )
        message(
            "- set the SYSTEMC_INCLUDE and SYSTEMC_LIBDIR (environment) variables; or"
        )
        message("- set SYSTEMC_ROOT (environment) variable; or")
        message("- set SYSTEMC (environment) variable; or")
        message(
            "- use the CMake module of your SystemC installation (may require CMAKE_PREFIX_PATH)"
        )
        message(FATAL_ERROR "SystemC not found")
    endif()
endfunction()

function(verilator_link_systemc TARGET)
    _verilator_find_systemc()
    target_link_libraries("${TARGET}" PUBLIC Verilator::systemc)
    target_compile_options(
        ${TARGET}
        PRIVATE $ENV{SYSTEMC_CXX_FLAGS} ${SYSTEMC_CXX_FLAGS}
    )
endfunction()

function(verilator_generate_key OUTPUT_VARIABLE)
    execute_process(
        COMMAND ${VERILATOR_BIN} --generate-key
        OUTPUT_VARIABLE KEY_VAL
        RESULT_VARIABLE KEY_RET
    )
    if(KEY_RET)
        message(FATAL_ERROR "verilator --generate-key failed")
    endif()
    string(STRIP ${KEY_VAL} KEY_VAL)
    set(${OUTPUT_VARIABLE} ${KEY_VAL} PARENT_SCOPE)
endfunction()
